#ifndef __EPSILON_3D__VECTOR3_H__
#define __EPSILON_3D__VECTOR3_H__

#include <math.h>
#include "vector.h"

#define DotProduct(x,y)        ((x)[0] * (y)[0]  +  (x)[1] * (y)[1]  +  (x)[2] * (y)[2])
#define CrossProduct(v1,v2,v3) {                       \
  (v3)[0] = ((v1 [1] * v2 [2])  -  (v1 [2] * v2 [1])); \
  (v3)[1] = ((v1 [2] * v2 [0])  -  (v1 [0] * v2 [2])); \
  (v3)[2] = ((v1 [0] * v2 [1])  -  (v1 [1] * v2 [0])); \
}

#include <stdlib.h> // abs

/**
 * A 3D Vector class
 */
class e3dVector3
{
public:
  union {
    vec3_t v;
      struct {
        vec_t  x,
                 y,
                   z;
      };
  };

  /**
   * Fast (uninitialized) vector construction
   */
  e3dVector3 (void) {}

  /**
   * Initialized vector construction
   *
   * All fields are initialized using m...
   */
  e3dVector3 (vec_t m) :
                         x (m),
                         y (m),
                         z (m) {}

  /**
   * Initialized vector construction
   *
   * Initialized using fx, fy and fz
   */
  e3dVector3 (vec_t vx, vec_t vy, vec_t vz = 0.0f) :
                                                     x (vx),
                                                     y (vy),
                                                     z (vz) {}

  /* Copy Constructor. */
  e3dVector3 (const e3dVector3& v) :
                                     x (v.x),
                                     y (v.y),
                                     z (v.z) {}

  /* Copy Constructor (float*). */
  e3dVector3 (const float* f) :
                                x (f [0]),
                                y (f [1]),
                                z (f [2]) {}

  /* Copy Constructor (float*). */
  e3dVector3 (float* f) :
                          x (f [0]),
                          y (f [1]),
                          z (f [2]) {}

  /* Quick vec_t* conversion (const) */
  inline operator const vec_t* (void) const
  {
    return v;
  }

  /* Quick vec_t* conversion */
  inline operator vec_t* (void)
  {
    return v;
  }

  /* Vector addition. */
  inline friend e3dVector3 operator+ (const e3dVector3& v1, const e3dVector3& v2)
  {
    return e3dVector3 ( v1.x + v2.x,
                        v1.y + v2.y,
                        v1.z + v2.z  );
  }

  /* Vector subtraction. */
  inline friend e3dVector3 operator- (const e3dVector3& v1, const e3dVector3& v2)
  {
    return e3dVector3 ( v1.x - v2.x,
                        v1.y - v2.y,
                        v1.z - v2.z  );
  }

  /* Vector dot product. */
  inline friend vec_t operator* (const e3dVector3& v1, const e3dVector3& v2)
  {
    return ( DotProduct (v1, v2) );
  }

  /* Vector cross product. */
  inline friend e3dVector3 operator% (const e3dVector3& v1, const e3dVector3& v2)
  {
    e3dVector3 vRet;
    CrossProduct (v1, v2, vRet);

    return vRet;
  }

  /* Vector cross product, result stored locally. */
  inline void Cross (const e3dVector3& px, const e3dVector3& py)
  {
    CrossProduct (px, py, *this);
  }

  /* Vector multiplication (vector * scalar) */
  inline friend e3dVector3 operator* (const e3dVector3& v, vec_t f)
  {
    return e3dVector3 ( v.x * f,
                        v.y * f,
                        v.z * f  );
  }

  /* Vector multiplication (scalar * vector) */
  inline friend e3dVector3 operator* (vec_t f, const e3dVector3& v)
  {
    return e3dVector3 ( v.x * f,
                        v.y * f,
                        v.z * f  );
  }

  /* Vector multiplication (vector * scalar <int>) */
  inline friend e3dVector3 operator* (const e3dVector3& v, int i)
  {
    return v * (vec_t)i;
  }

  /* Vector multiplication (scalar <int> * vector) */
  inline friend e3dVector3 operator* (int i, const e3dVector3& v)
  {
    return v * (vec_t)i;
  }

  /* Vector division (vector / scalar). */
  inline friend e3dVector3 operator/ (const e3dVector3& v, vec_t f)
  {
    f = (1.0f / f);

    return e3dVector3 ( v.x * f,
                        v.y * f,
                        v.z * f  );
  }

  /* Vector division (vector / scalar <int>). */
  inline friend e3dVector3 operator/ (const e3dVector3& v, int i)
  {
    return v / (vec_t)i;
  }

  /* Equality operator (self explanitory). */
  inline friend bool operator== (const e3dVector3& v1, const e3dVector3& v2)
  {
    return ( v1.x == v2.x  &&
             v1.y == v2.y  &&
             v1.z == v2.z     );
  }

  /* Inequality operator (self explanitory). */
  inline friend bool operator!= (const e3dVector3& v1, const e3dVector3& v2)
  {
    return ( v1.x != v2.x  ||
             v1.y != v2.y  ||
             v1.z != v2.z     );
  }

  /* Vector projection. */
  inline friend e3dVector3 operator>> (const e3dVector3& v1, const e3dVector3& v2)
  {
    return ((v2 * (v1 * v2)) / (v2 * v2));
  }

  /* Vector projection. */
  inline friend e3dVector3 operator<< (const e3dVector3& v1, const e3dVector3& v2)
  {
    return ((v1 * (v1 * v2)) / (v1 * v1));
  }

  /* Vector epsilon comparison (less). */
  inline friend bool operator< (const e3dVector3& v, vec_t f)
  {
    return ( ABS (v.x) < f  &&
             ABS (v.y) < f  &&
             ABS (v.z) < f     );
  }

  /* Vector epsilon comparison (less). */
  inline friend bool operator> (vec_t f, const e3dVector3& v)
  {
    return ( ABS (v.x) < f  &&
             ABS (v.y) < f  &&
             ABS (v.z) < f     );
  }

  /* Returns the n'th component of this vector. */
  inline vec_t operator[] (int n) const
  {
    return (!n ?  x : ((n & 1) ?  y : z));
  }

  /* Returns the n'th component of this vector. */
  inline vec_t& operator[] (int n)
  {
    return (!n ?  x : ((n & 1) ?  y : z));
  }



  /* ** Operations that change this vector using another. ** */

  /* Vector addition (THIS). */
  inline e3dVector3& operator+= (const e3dVector3& v)
  {
    x += v.x;
    y += v.y;
    z += v.z;

    return *this;
  }

  /* Vector subtraction (THIS). */
  inline e3dVector3& operator-= (const e3dVector3& v)
  {
    x -= v.x;
    y -= v.y;
    z -= v.z;

    return *this;
  }

  /* Vector multiplication (THIS * scalar). */
  inline e3dVector3& operator*= (vec_t f)
  {
    x *= f;
    y *= f;
    z *= f;

    return *this;
  }

  /* Vector multiplication (THIS * vector). */
  inline e3dVector3& operator*= (const e3dVector3& _v)
  {
    x *= _v.x,
    y *= _v.y,
    z *= _v.z;

    return *this;
  }

  /* Vector division (THIS / scalar). */
  inline e3dVector3& operator/= (vec_t f)
  {
    f = (1.0f / f);

    x *= f;
    y *= f;
    z *= f;

    return *this;
  }

  /* Vector division (THIS / vector). */
  inline e3dVector3& operator/= (const e3dVector3& _v)
  {
    x /= _v.x,
    y /= _v.y,
    z /= _v.z;

    return *this;
  }

  /* Unary + operator. */
  inline e3dVector3 operator+ (void) const
  {
    return *this;
  }

  /* Unary - operator. */
  inline e3dVector3 operator- (void) const
  {
    return e3dVector3 ( -x,
                        -y,
                        -z  );
  }

  /* Comparison == operator. */
  inline bool operator== (const e3dVector3& _v)
  {
    return ( x == _v.x  &&
             y == _v.y  &&
             z == _v.z     );
  }

  /* Comparison != operator. */
  inline bool operator!= (const e3dVector3& _v)
  {
    return !(*this  == _v);
  }

  /* Set the value of this vector. */
  inline void Set (vec_t sx, vec_t sy, vec_t sz)
  {
    x = sx;
    y = sy;
    z = sz;
  }

  /* Set the value of this vector. */
  inline void Set (const e3dVector3& v)
  {
    x = v.x;
    y = v.y;
    z = v.z;
  }

  /* Return the normal of this vector. */
  inline vec_t Normal (void) const
  {
    return (vec_t) sqrt ( x * x  +
                          y * y  +
                          z * z    );
  }

  /* Return the squared normal (magnitude) of this vector. */
  inline vec_t SquaredNormal (void) const
  {
    return ( x * x  +
             y * y  +
             z * z    );
  }

  /* Return the length of this vector. */
  inline friend vec_t Length (const e3dVector3& v1)
  {
    return (vec_t)sqrt (v1.SquaredNormal ());
  }

  /**
   * Returns the unit vector in the direction of this vector.
   * Attempting to normalize a zero-vector will result in a divide by
   * zero error.  This is as it should be... fix the calling code.
   */
  inline e3dVector3 Unit (void) const
  {
    return (*this) / (this->Normal ());
  }

  /* Return the norm (magnitude) of a vector. */
  inline static vec_t Normal (const e3dVector3& v)
  {
    return v.Normal ();
  }

  /* Normalizes a vector to a unit vector. */
  inline static e3dVector3 Unit (const e3dVector3& v)
  {
    return v.Unit ();
  }

  /* Perform linear interpolation from v1 to v2, t should be in [0.0, 1.0] */
  inline static e3dVector3 lerp (const e3dVector3& v1, const e3dVector3& v2, const float& t) {
    return (1.0f - t) * v1 + (t * v2);
  }

  /* Average this vector with another */
  inline e3dVector3 Average (const e3dVector3& v1)
  {
    return ((*this + v1) * 0.5f);
  }

  /* Average two vectors (allow static usage) */
  inline static e3dVector3 Average ( const e3dVector3& v1,
                                     const e3dVector3& v2 )
  {
    return ((v1 + v2) * 0.5f);
  }

  /* Scale this vector to length = 1.0. */
  inline void Normalize (void)
  {
    double u = x * x + y * y + z * z;
    if (abs ((int)u - 1) < 1e-12) {
      return;
    }

    if (u) {
      u = 1.0f / (vec_t)sqrt (u);
      x *= (vec_t) u;
      y *= (vec_t) u;
      z *= (vec_t) u;
    }
  }

  /* Check if the vector is zero. */
  inline bool IsZero (void) const
  {
    return ( (x == 0)  &&
             (y == 0)  &&
             (z == 0)     );
  }
};

#endif /* __EPSILON_3D__VECTOR3_H__ */
